# -*- coding: UTF-8 -*-
'''
@Project ：automation_-interface_testing
@File    ：logs.py
@IDE     ：PyCharm
@Author  ：思亦忆
@Date    ：2025/9/5 星期二 21:55:31
'''

"""
自动化接口测试 - 日志模块 (最终修复版)
修复问题：
1. login.py调用生成多个日志文件的问题
2. 正确识别调用者文件名
3. 避免生成多余的app_logger.log文件
"""

import logging
import logging.config
import yaml
import os
import sys
import shutil
import gzip
from datetime import datetime
import inspect
from pathlib import Path
import traceback
import threading
import time
import json
from typing import Optional, Dict, Any
from concurrent.futures import ThreadPoolExecutor


class DynamicLogging:
    """动态日志配置类 - 最终修复版"""

    def __init__(self, config_path: str = 'config/logging_config.yaml',
                 logs_dir: Optional[str] = None,
                 module_name: str = 'app_logger'):
        """
        初始化日志管理器

        Args:
            config_path: 日志配置文件路径
            logs_dir: 日志目录路径
            module_name: 模块名称
        """
        self.config_path = config_path
        self.module_name = module_name
        self.current_log_file: Optional[Path] = None
        self.logger: Optional[logging.Logger] = None
        self._lock = threading.RLock()

        # 性能统计
        self._stats = {
            'total_logs': 0,
            'last_log_time': None,
            'start_time': time.time(),
            'errors': 0,
            'warnings': 0
        }

        # 异步日志记录
        self._executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix=f"LogAsync_{module_name}")

        # 🔧 根据图片中的项目结构确定日志目录
        self.logs_dir = self._resolve_logs_dir(logs_dir)

        # 确保日志目录存在
        self._ensure_logs_dir()

        print(f"✅ 日志目录: {self.logs_dir}")

    def _resolve_logs_dir(self, logs_dir: Optional[str]) -> Path:
        """解析日志目录路径 - 根据图片中的项目结构"""
        if logs_dir:
            return Path(logs_dir)

        # 🔧 根据图片：项目根目录是 automation_-interface_testing
        current_file = Path(__file__).absolute()
        print(f"📁 当前文件位置: {current_file}")

        # 项目根目录：上两级目录 (src/common -> src -> 项目根目录)
        project_root = current_file.parent.parent.parent
        expected_logs_dir = project_root / 'logs'

        if self._check_dir_writable(expected_logs_dir):
            return expected_logs_dir

        cwd_logs = Path.cwd() / 'logs'
        if self._check_dir_writable(cwd_logs):
            print(f"⚠️ 使用工作目录日志: {cwd_logs}")
            return cwd_logs

        import tempfile
        temp_dir = Path(tempfile.gettempdir()) / 'automation_logs'
        print(f"🔄 使用临时目录: {temp_dir}")
        return temp_dir

    def _check_dir_writable(self, directory: Path) -> bool:
        """检查目录是否可写"""
        try:
            directory.mkdir(exist_ok=True, parents=True)
            test_file = directory / ".write_test"
            test_file.write_text("write test")
            test_file.unlink()
            return True
        except Exception as e:
            print(f"❌ 目录不可写 {directory}: {e}")
            return False

    def _ensure_logs_dir(self) -> None:
        """确保日志目录存在"""
        try:
            self.logs_dir.mkdir(exist_ok=True, parents=True)
        except Exception as e:
            print(f"❌ 无法创建日志目录: {e}")
            import tempfile
            self.logs_dir = Path(tempfile.gettempdir()) / 'automation_logs'
            self.logs_dir.mkdir(exist_ok=True)
            print(f"🔄 使用备用目录: {self.logs_dir}")

    def _generate_log_filename(self, caller_filename: str = None):
        """生成日志文件名 - 🔧 修复：使用正确的调用者文件名"""
        # 使用要求的格式 "年月日 时分秒 调用文件名.log"
        timestamp = datetime.now().strftime('%Y-%m-%d %H-%M-%S')

        # 🔧 修复：优先使用传入的调用者文件名，避免重复分析调用栈
        if caller_filename:
            caller = caller_filename
        else:
            caller = self._get_real_caller_filename()

        # 格式为 "年月日 时分秒 调用文件名.log"
        filename = f"{timestamp} {caller}.log"

        log_path = self.logs_dir / filename
        self.current_log_file = log_path

        print(f"📄 生成的日志文件: {log_path}")
        return log_path

    def _get_real_caller_filename(self) -> str:
        """🔧 修复：准确获取真实调用者文件名"""
        try:
            # 获取完整的调用栈
            stack = inspect.stack()

            # 跳过日志模块本身的帧
            # 我们需要找到第一个调用 get_logger 或使用日志的模块
            for i, frame_info in enumerate(stack):
                filename = frame_info.filename
                function_name = frame_info.function

                # 跳过日志模块本身
                if 'logs.py' in filename or 'logging' in filename:
                    continue

                # 跳过标准库和第三方库
                if ('site-packages' in filename or
                        'lib/python' in filename or
                        filename.startswith('<') or
                        filename.endswith('>')):
                    continue

                # 找到第一个非日志相关的调用者
                caller_filename = Path(filename).stem
                print(f"🔍 找到真实调用者: {caller_filename} (函数: {function_name}, 文件: {filename})")

                # 特别处理：如果调用者是 get_logger 函数本身，需要继续查找
                if function_name == 'get_logger' and i + 1 < len(stack):
                    # 查看get_logger的调用者
                    next_frame = stack[i + 1]
                    next_filename = next_frame.filename
                    if 'logs.py' not in next_filename and 'logging' not in next_filename:
                        next_caller = Path(next_filename).stem
                        print(f"🔍 get_logger的调用者: {next_caller}")
                        return next_caller

                return caller_filename

            return 'main'

        except Exception as e:
            print(f"❌ 获取调用者文件名失败: {e}")
            return 'unknown'

    def setup_logging(self, caller_filename: str = None):
        """设置日志配置 - 🔧 修复：传入调用者文件名避免重复分析"""
        try:
            # 1. 查找并加载配置文件
            config_content = self._load_config_file()
            if not config_content:
                print("⚠️ 使用内置基础配置")
                return self._setup_basic_logging(caller_filename)

            # 2. 🔧 修复：生成正确的日志文件路径，传入调用者文件名
            log_filepath = self._generate_log_filename(caller_filename)
            log_filepath.parent.mkdir(parents=True, exist_ok=True)

            # 3. 更新配置文件中的路径
            config_content = self._update_config_with_dynamic_paths(config_content, str(log_filepath))

            # 4. 应用配置
            logging.config.dictConfig(config_content)
            self.logger = logging.getLogger('app_logger')

            # 5. 🔧 修复：立即测试日志记录
            self._test_logging_immediately(log_filepath)

            print(f"✅ 日志系统初始化成功，文件: {log_filepath}")
            return self.logger

        except Exception as e:
            print(f"❌ 配置加载失败: {e}")
            traceback.print_exc()
            return self._setup_basic_logging(caller_filename)

    def _load_config_file(self) -> Optional[Dict]:
        """加载配置文件"""
        config_paths = [
            Path(self.config_path),
            Path.cwd() / self.config_path,
            Path(__file__).parent.parent.parent / self.config_path,
        ]

        for config_path in config_paths:

            if config_path.exists():

                try:
                    with open(config_path, 'r', encoding='utf-8') as f:
                        return yaml.safe_load(f)
                except Exception as e:

                    return None

        return None

    def _update_config_with_dynamic_paths(self, config: Dict, log_filepath: str) -> Dict:
        """更新配置中的动态路径"""
        if 'handlers' in config and 'file_handler' in config['handlers']:
            config['handlers']['file_handler']['filename'] = log_filepath

        return config

    def _setup_basic_logging(self, caller_filename: str = None):
        """基础日志配置 -

        """
        try:
            log_filepath = self._generate_log_filename(caller_filename)
            log_filepath.parent.mkdir(parents=True, exist_ok=True)

            self.logger = logging.getLogger('app_logger')
            self.logger.setLevel(logging.DEBUG)
            self.logger.handlers.clear()

            # 文件处理器
            file_handler = logging.handlers.TimedRotatingFileHandler(
                log_filepath, when='midnight', backupCount=30, encoding='utf-8'
            )

            # 控制台处理器
            console_handler = logging.StreamHandler(sys.stdout)
            console_handler.setLevel(logging.INFO)

            # 格式化器
            formatter = logging.Formatter(
                '%(asctime)s | %(levelname)-8s | %(name)s | %(filename)s:%(lineno)d | %(message)s',
                datefmt='%Y-%m-%d %H:%M:%S'
            )

            file_handler.setFormatter(formatter)
            console_handler.setFormatter(formatter)

            self.logger.addHandler(file_handler)
            self.logger.addHandler(console_handler)

            # 立即测试
            self.logger.info("=" * 60)
            self.logger.info("🔧 基础日志配置已启用")
            self.logger.info(f"📁 日志文件: {log_filepath.name}")
            self.logger.info("=" * 60)

            return self.logger

        except Exception as e:

            logging.basicConfig(level=logging.DEBUG)
            self.logger = logging.getLogger('app_logger')
            self.logger.error("使用最基础的控制台日志")
            return self.logger

    def _test_logging_immediately(self, log_filepath: Path):
        """立即测试日志记录"""
        if self.logger:
            # 立即记录测试信息
            self.logger.info("=" * 60)
            self.logger.info("🎯 日志系统测试开始")
            self.logger.info(f"📁 日志目录: {self.logs_dir}")
            self.logger.info(f"📄 日志文件: {log_filepath.name}")
            self.logger.info(f"⏰ 启动时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            self.logger.info("=" * 60)

            # 强制刷新，确保写入文件
            for handler in self.logger.handlers:
                handler.flush()

            # 验证文件写入
            self._verify_log_file(log_filepath)

    def _verify_log_file(self, log_filepath: Path):
        """验证日志文件是否已创建并有内容"""
        try:
            if log_filepath.exists():
                file_size = log_filepath.stat().st_size

                if file_size > 0:

                    return True
                else:

                    return False
            else:

                return False
        except Exception as e:

            return False

    def get_log_file_info(self) -> Dict[str, Any]:
        """获取日志文件信息"""
        try:
            if not self.current_log_file:
                return {'error': '日志文件未初始化'}

            current_file = self.current_log_file
            file_info = {
                'current_file': str(current_file),
                'file_size': 0,
                'exists': False,
                'directory': str(self.logs_dir)
            }

            if current_file.exists():
                stat = current_file.stat()
                file_info.update({
                    'file_size': stat.st_size,
                    'exists': True,
                    'created_time': datetime.fromtimestamp(stat.st_ctime).strftime('%Y-%m-%d %H:%M:%S')
                })

            return file_info

        except Exception as e:
            return {'error': str(e)}

    def health_check(self) -> Dict[str, Any]:
        """健康检查"""
        health = {
            'status': 'healthy',
            'timestamp': datetime.now().isoformat(),
            'issues': []
        }

        # 检查日志目录
        if not self.logs_dir.exists():
            health['issues'].append('日志目录不存在')
            health['status'] = 'error'
        else:
            try:
                test_file = self.logs_dir / '.health_check'
                test_file.write_text('test')
                test_file.unlink()
            except Exception as e:
                health['issues'].append(f'目录不可写: {e}')
                health['status'] = 'error'

        # 检查当前日志文件
        if self.current_log_file:
            if self.current_log_file.exists():
                size = self.current_log_file.stat().st_size
                if size == 0:
                    health['issues'].append('日志文件存在但为空')
                    health['status'] = 'warning'
            else:
                health['issues'].append('当前日志文件不存在')
                health['status'] = 'warning'

        health['logs_dir'] = str(self.logs_dir)
        health['current_file'] = str(self.current_log_file) if self.current_log_file else None

        return health


# 全局管理器字典
_logging_managers = {}


def get_logger(config_path='config/logging_config.yaml', module_name='app_logger', logs_dir=None):
    """获取日志记录器 - 🔧 修复：准确传递调用者文件名"""
    global _logging_managers

    # 🔧 修复：首先获取调用者文件名
    caller_filename = _get_caller_filename_for_logging()

    # 🔧 修复：使用调用者文件名作为key的一部分，确保不同文件使用不同的logger实例
    key = f"{config_path}_{caller_filename}"

    if key not in _logging_managers:

        try:
            manager = DynamicLogging(config_path, logs_dir, module_name)
            # 🔧 修复：传入调用者文件名，避免在类内部重复分析调用栈
            manager.setup_logging(caller_filename)
            _logging_managers[key] = manager



        except Exception as e:

            traceback.print_exc()
            manager = DynamicLogging(module_name=module_name)
            manager._setup_basic_logging(caller_filename)
            _logging_managers[key] = manager

    return _logging_managers[key].logger


def _get_caller_filename_for_logging() -> str:
    """🔧 修复：专门为日志系统获取调用者文件名"""
    try:
        # 获取调用栈
        stack = inspect.stack()

        # 跳过日志模块本身的帧
        for frame_info in stack:
            filename = frame_info.filename
            function_name = frame_info.function

            # 跳过日志相关的文件
            if ('logs.py' in filename or
                    'logging' in filename or
                    'site-packages' in filename or
                    filename.startswith('<') or
                    filename.endswith('>')):
                continue

            # 返回第一个非日志相关的调用者文件名
            caller_filename = Path(filename).stem
            return caller_filename

        return 'main'
    except Exception as e:

        return 'unknown'


def debug_logging_system():
    """调试日志系统状态"""
    print("\n" + "=" * 60)
    print("🐛 日志系统调试信息")
    print("=" * 60)

    if not _logging_managers:
        return

    for key, manager in _logging_managers.items():

        health = manager.health_check()

        file_info = manager.get_log_file_info()
        if 'error' in file_info:
            print(f"   文件信息: {file_info['error']}")
        else:
            print(f"   当前文件: {file_info.get('current_file', '未知')}")
            print(f"   文件存在: {file_info.get('exists', False)}")
            print(f"   文件大小: {file_info.get('file_size', 0)} bytes")

        if health['issues']:
            print("   问题列表:")
            for issue in health['issues']:
                print(f"     - {issue}")


# 测试函数
def test_logging_from_different_files():
    """模拟从不同文件调用日志系统"""

    # 模拟从 login.py 调用
    print("\n1. 模拟从 login.py 调用:")
    logger1 = get_logger(module_name='login')
    logger1.info("这是从 login.py 记录的日志")

    # 模拟从其他文件调用
    print("\n2. 模拟从 common_requests.py 调用:")
    logger2 = get_logger(module_name='common_requests')
    logger2.info("这是从 common_requests.py 记录的日志")

    # 显示系统状态
    debug_logging_system()


# 如果直接运行此文件，进行测试
if __name__ == "__main__":
    test_logging_from_different_files()
    print("\n✅ 测试完成，请检查 logs 目录下的文件")
